# -*- coding: utf-8 -*-
# =============================================================================
#         Desc: 使用mysqlbinlog命令对binlog进行分析找出
#               1、执行时间较长事务。
#               2、影响行数较多事务。
#               3、产生Binlog量较大的事务。
#       Author: GGA
#        Email:
#     HomePage:
#      Version: 1.0.0
#   LastChange: 2020-12-20
#      History:
# =============================================================================
import os
import sys
import re
import subprocess
import logging
import fire
import datetime
from utils.logger_helper import LoggerHelper

logger = LoggerHelper.get_logger()


class CommonConfig(object):
    # mysqlbinlog文件路径
    mysql_binlog_exe = "mysqlbinlog"


class TableAction(object):
    def __init__(self, table_name):
        self.table_name = table_name
        self.insert = 0
        self.update = 0
        self.delete = 0

    @property
    def total(self):
        return self.insert + self.update + self.delete


class BinlogTimeChecker(object):
    @classmethod
    def get_binlog_datetime(cls, binlog_line):
        """
        解析记录行中包含的时间
        :param binlog_line:
        :return:
        """
        if binlog_line.find("server id") < 0:
            return None
        start_index = 1
        end_index = binlog_line.index("server id", start_index)
        dt_str = binlog_line[start_index:end_index].strip().replace("#", "").strip()
        re_pattern = r'\d{6}\s{1,2}\d{1,2}:\d{2}:\d{2}'
        re_match = re.match(re_pattern, dt_str)
        if re_match:
            dt_str = "20" + dt_str
        dt_tmp = datetime.datetime.strptime(dt_str, "%Y%m%d %H:%M:%S")
        return dt_tmp

    @classmethod
    def show_time_result(cls, time_dict):
        time_list = sorted(time_dict.values(), key=lambda x: x["TIME"])
        check_results = list()
        check_results.append("")
        check_results.append("每秒操作统计如下:")
        table_action_format = "|{:>30}|{:>10}|{:>10}|{:>10}|{:>10}|"
        table_header1 = table_action_format.format("-" * 30, "-" * 10, "-" * 10, "-" * 10, "-" * 10)
        check_results.append(table_header1)
        table_header2 = table_action_format.format("TIME", "INSERT", "UPDATE", "DELETE", "TOTAL")
        check_results.append(table_header2)
        check_results.append(table_header1)
        row_count = 0
        for item_value in time_list:
            msg = table_action_format.format(
                item_value["TIME"],
                item_value["INSERT"], item_value["UPDATE"],
                item_value["DELETE"], item_value["TOTAL"]
            )
            check_results.append(msg)
            row_count += 1
            if row_count % 20 == 0:
                check_results.append(table_header1)
                check_results.append(table_header2)
                check_results.append(table_header1)
            pass
        check_results.append(table_header1)
        logger.info("\r\n".join(check_results))

    @classmethod
    def get_time_summary(cls, binlog_path: str, start_time: str = None, stop_time: str = None):
        logger.info("开始检查文件：{}".format(binlog_path))
        cmd = " {} -v --base64-output=DECODE-ROWS '{}' "
        cmd = cmd.format(
            CommonConfig.mysql_binlog_exe, binlog_path
        )
        if start_time is not None:
            cmd += " --start-datetime='{}' ".format(start_time)
        if stop_time is not None:
            cmd += " --stop-datetime='{}' ".format(stop_time)
        cmd += " | egrep '^### INSERT INTO|^### UPDATE|^### DELETE FROM|server id' "
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        line_num = 0
        time_dict = dict()
        current_time = None
        current_time_str = ""
        while True:
            line_num += 1
            if line_num % 10000 == 0:
                logger.info("处理记录行：{}".format(line_num))
            file_bytes = p.stdout.readline()
            if not file_bytes:
                break
            file_line = file_bytes.decode().strip()
            if str(file_line).find("server id") > 0:
                current_time = cls.get_binlog_datetime(binlog_line=file_line)
                current_time_str = current_time.strftime("%Y-%m-%d %H:%M:%S")
            if current_time_str not in time_dict:
                time_dict[current_time_str] = {
                    "TIME": current_time_str,
                    "INSERT": 0, "UPDATE": 0, "DELETE": 0, "TOTAL": 0
                }
            if file_line.startswith("### INSERT INTO"):
                time_dict[current_time_str]["INSERT"] += 1
                time_dict[current_time_str]["TOTAL"] += 1
            if file_line.startswith("### UPDATE"):
                time_dict[current_time_str]["UPDATE"] += 1
                time_dict[current_time_str]["TOTAL"] += 1
            if file_line.startswith("### DELETE FROM"):
                time_dict[current_time_str]["DELETE"] += 1
                time_dict[current_time_str]["TOTAL"] += 1
        logger.info("检查文件完成：{}".format(binlog_path))
        cls.show_time_result(time_dict)


def init_logger():
    LoggerHelper.init_logger(logger_level=logging.INFO)
    global logger
    logger = LoggerHelper.get_logger()


def main(binlog_path="", start_time: str = None, stop_time: str = None):
    """
    获取指定BINLOG内每个表的修改操作次数
    :param binlog_path: binlog文件地址
    :param start_time: 开始时间,格式为 2023-01-01 12:00:00
    :param stop_time: 结束时间,格式为 2023-01-01 12:00:00
    :return:
    """
    if not os.path.exists(binlog_path):
        logger.info("指定的文件不存在,请重试！")
        return
    BinlogTimeChecker.get_time_summary(binlog_path, start_time, stop_time)


if __name__ == '__main__':
    init_logger()
    fire.Fire(main)
